#include "blsvn.h"
#include "str.h"

#define WSTR_DEFAULT_SIZE   (128)

void WStr::Init(size_t NewSize, const wchar_t* Other, size_t CopyLen)
    {
    size_t      OtherLen = 0;

    if(Other)       /* if we've been given an initial string value      */
        OtherLen    = lstrlenW(Other);
    if(CopyLen == 0)    /* if caller wants us to calculate copy length  */
        CopyLen     = OtherLen;
    else if(CopyLen > OtherLen)
        CopyLen     = OtherLen;

    /* now we know how many bytes will be copied to our new string      */
    if(NewSize <= CopyLen) /* if caller-indicated size is insufficient     */
        {
        if(CopyLen == 0)
            NewSize    = WSTR_DEFAULT_SIZE;
        else
            NewSize    = CopyLen + 1;
        }
        
    String      = (wchar_t*)WcMalloc((NewSize+1)*sizeof(wchar_t));
    if(Other && CopyLen)
        WcMemCpy(String, Other, sizeof(*Other)*CopyLen);
    String[CopyLen] = L'\0';
    Length          = CopyLen;
    Size            = NewSize;
    }

void WStr::Shrink()
    {
    size_t  NewSize;

    NewSize     = Length + 1;
    WCASSERT(Size >= NewSize);
    
    if(Size < (NewSize + 8))    /* don't shrink if it don't buy us much */
        {
        String = (wchar_t*)WcRealloc(String, NewSize * sizeof(wchar_t));
        Size   = NewSize;
        }
    }

WStr::WStr(size_t Size)
    {
    Init(Size);
    }
WStr::~WStr()
    {
    WcFree(String);
    }

WStr::WStr(const WStr& Other)
    {
    Init(0, Other.String, Other.Length);
    }

WStr::WStr(const wchar_t* Other, size_t CopyLen)
    {
    Init(0, Other, CopyLen);
    WCASSERT(Length >= CopyLen);
    }

WStr& WStr::operator=(const WStr& Other)
    {
    if(String)
        WcFree(String);
    Init(0, Other.String, Other.Length);
    return *this;
    }

WStr& WStr::operator=(const wchar_t* Other)
    {
    if(String)
        WcFree(String);
    Init(0, Other, 0);
    return *this;
    }

/* ExpandEnvVars() - expand any environment variables in this string.
 *
 * Environment variables may appear in the form of %var% in the string.
 * This function locates them and replaces them with the environment
 * string they refer to.
 */
WStr& WStr::ExpandEnvVars()
    {
    WStr    Temp(BLSVN_MAX_PATH);
    DWORD   NChars;

    WCASSERT(Length > 0);
    
    /* returns count of chars copied, including NUL */
    NChars  = ExpandEnvironmentStringsW(String, Temp.String, Temp.Size);
    WCASSERT(NChars > 0);

    Temp.Length  = NChars;
    *this   = Temp;

    Shrink();

    return *this;
    }

WStr* WStr::Append(const wchar_t* Suffix)
    {
    size_t  NewLength;
    size_t  SuffixLen   = lstrlenW(Suffix);

    if(Suffix != NULL)
        {
        if(SuffixLen == 0)
            SuffixLen   = lstrlenW(Suffix);
        if(SuffixLen)
            {
            NewLength   = SuffixLen + Length;
            if(NewLength+1 > Size)
                {
                String = (wchar_t*)WcRealloc(String, (NewLength+1)*sizeof(wchar_t));
                Size   = NewLength+1;
                }
            WcMemCpy(String + Length, Suffix, SuffixLen * sizeof(wchar_t));
            Length = NewLength;
            WCASSERT(Size > Length);
            String[Length]    = L'\0';
            }
        }

    return this;
    }

WStr*   WStr::ForceTrailingSlash(wchar_t Slash)
    {
    if(Length == 0 || !ISSLASH(String[Length-1]))
        {
        wchar_t     Str[2];

        Str[0]  = Slash;
        Str[1]  = L'\0';
        Append(Str);
        }

    return this;
    }

/* TrimDir() - trim a directory off the end of a path.
 *
 * Not a particularly robust implementation, but we know our
 * only caller is very disciplined.
 */
WStr*   WStr::TrimDir()
    {
    wchar_t*  Rover;

    Rover   = String + Length;

    while(--Rover >= String)
        {
        if(ISSLASH(*Rover))
            {
            *Rover  = L'\0';
            Length  = lstrlenW(String);
            break;
            }
        }
    return this;
    }


WStr* WStr::Quoted(const wchar_t* Path)
    {
    WStr*   Result;
    size_t  PathLen;

    PathLen     = lstrlenW(Path);
    Result      = new WStr(PathLen + 4);
    lstrcpyW(Result->String, Path);

    PathQuoteSpacesW(Result->String);

    return Result;
    }

/*  UrlCanonicalize() - wrapper for Windows API function of same name.
 *                                                                
 */
HRESULT     WStr::UrlCanonicalize(WStr* Input, DWORD Flags)
    {
    HRESULT Result;
    DWORD   NBytes;

    NBytes  = Size;
    Result  = UrlCanonicalizeW(Input->String, String, &NBytes, Flags);
    WCASSERT(NBytes < Size);
    if(Result == S_OK)
        {
        Length  = NBytes;
        WCASSERT(String[Length] == L'\0');
        }
    return Result;
    }

WStr* WStr::UrlFromPath(const wchar_t* Path)
    {
    HRESULT Status;
    WStr*   Result = new WStr(BLSVN_MAX_PATH);

    if(StrHasScheme(Path))
        {
        Result  = new WStr(Path);
        }
    else
        {
        DWORD   Size;
        WStr*   FullPath;

        FullPath    = new WStr(BLSVN_MAX_PATH);
        Size        = FullPath->Size;
        Size        = GetFullPathNameW(Path, Size, FullPath->WChar(), NULL);
        WCASSERT(Size != 0);
        WCASSERT(Size < BLSVN_MAX_PATH);
        FullPath->Length    = Size;

        Result  = new WStr(BLSVN_MAX_PATH);
        Size    = Result->Size;
        Status  = UrlCreateFromPathW(FullPath->WChar(), Result->WChar(), &Size, 0);
    
        WCASSERT(Status == S_OK || Status == S_FALSE);
        WCASSERT(Size < Result->Size);
        Result->Length  = Size;

        delete FullPath;
        }

    Result->Shrink();

    return Result;
    }


WStr* WStr::FromXml8(const char* Start, const char* End)
    {
    int         ConvertStatus;
    WStr*       Utf16   = new WStr(BLSVN_MAX_PATH);
    WStr*       Result  = new WStr(BLSVN_MAX_PATH);
    wchar_t*    Input   = Utf16->String;
    wchar_t*    Output  = Result->String;

    WCASSERT(End > Start);
    
    ConvertStatus   = MultiByteToWideChar(
        CP_UTF8,
        0,
        Start,
        End - Start,
        Utf16->String,
        BLSVN_MAX_PATH);

    WCASSERT(ConvertStatus > 0);
    while(*Input)
        {
        if(*Input == L'&')
            {
            ++Input;
            if(!StrCmpNIW(L"amp;", Input, 4))
                {
                Input      += 4;
                *Output++   = L'&';
                }
            else if(!StrCmpNIW(L"quot;", Input, 5))
                {
                Input      += 5;
                *Output++   = L'"';
                }
            else if(!StrCmpNIW(L"apos;", Input, 5))
                {
                Input      += 5;
                *Output++   = L'\'';
                }
            else if(!StrCmpNIW(L"lt;", Input, 3))
                {
                Input      += 3;
                *Output++   = L'<';
                }
            else if(!StrCmpNIW(L"gt;", Input, 3))
                {
                Input      += 3;
                *Output++   = L'>';
                }
            else
                WCASSERT(FALSE);
            }
        else
            {
            WCASSERT((size_t)(Output - Result->String) < Result->Size);
            *Output++   = *Input++;
            }
        }
    *Output++       = L'\0';
    Result->Length  = Output - Result->String;
    delete Utf16;

    Result->Shrink();

    return Result;
    }

WStr* WStr::FromProfile(const wchar_t* Section, const wchar_t* Name, const wchar_t* Path)
    {
    DWORD       NBytes;
    WStr*       Result;

    Result      = new WStr(BLSVN_MAX_PATH);
    NBytes      = GetPrivateProfileStringW(
                        Section,
                        Name,
                        NULL,
                        Result->String,
                        BLSVN_MAX_PATH,
                        Path
                        );
    /* we have big limits; assert we won't hit them */
    WCASSERT(NBytes < BLSVN_MAX_PATH);

    Result->Length  = NBytes;
    if(NBytes == 0)
        {
        delete Result;
        Result      = NULL;
        }
    else
        Result->Shrink();

    return Result;
    }


WStr*   WStr::FromTempFile()
    {
    static WStr*    TempPath;
    WStr*           Result;
    UINT            Unique;

    /* if we haven't stored a path from a previous call */
    if(TempPath == NULL)
        {
        DWORD   Length;
        
        TempPath    = new WStr(BLSVN_MAX_PATH);
        Length      = BLSVN_MAX_PATH;
        Length      = GetTempPathW(Length, TempPath->WChar());
        WCASSERT(Length < (DWORD)BLSVN_MAX_PATH);
        WCASSERT(Length != 0);
        }
    Result  = new WStr(BLSVN_MAX_PATH);
    Unique  = GetTempFileNameW(TempPath->WChar(),
                              L"SVN",
                              0,
                              Result->WChar());
    WCASSERT(Unique != 0);
    Result->Length  = lstrlenW(Result->String);

    return Result;
    }

/* StrHasScheme() - does it start with a URL scheme?
 *
 * Mainly, we are trying to distinguish a Windows path from
 * something that might be a file:// or http:// URI.
 */
int         StrHasScheme(const wchar_t* Str)
    {
    const wchar_t*  Rover;
    wchar_t         Char;
    int             Result  = FALSE;    /* assume no scheme */

    Rover   = Str;

    while((Char=*Rover) != L'\0')
        {
        if(ISSLASH(Char) || Char == L':')
            break;
        else
            ++Rover;
        }

    if(Char == L':' && Rover - Str > 1 && Rover[1] == L'/')
        Result  = TRUE;

    return Result;
    }



